package com.ass.freedom.net.boot;

import com.ass.freedom.net.handler.FMessageHandler;
import com.ass.freedom.net.handler.ServerHandler;
import com.ass.freedom.net.handler.decode.FrameDecoder;
import com.ass.freedom.net.handler.decode.FrameToMessageDecoder;
import com.ass.freedom.net.handler.encode.FrameEncoder;
import com.ass.freedom.net.handler.encode.MessageToFrameEncoder;
import com.ass.freedom.net.protocol.FMTraits;
import com.ass.freedom.net.protocol.FMessage;
import com.ass.freedom.net.ssl.SecureSocketSslContextFactory;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.util.concurrent.ScheduledFuture;
import io.netty.util.internal.SystemPropertyUtil;
import lombok.extern.slf4j.Slf4j;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import java.security.KeyStore;
import java.util.concurrent.TimeUnit;

/**
 * netty 服务端引导启动
 *
 * @author syq
 * @date 2019/12/23
 */
@Slf4j
public class NettyBootstrapServer implements BootstrapServer {

    private static final String H_FRAME_DECODER = "FrameDecoder";

    private static final String H_FRAME_ENCODER = "FrameEncoder";

    private static final String H_READ_TIMEOUT = "ReadTimeout";

    private static final String H_FM_DECODER = "FMessageDecoder";

    private static final String H_FM_ENCODER = "FMessageEncoder";

    private static final String H_FM_HANDLER = "FMessageHandler";

    private static final int MIN_READ_TIMEOUT = 30;


    private ServerProperty serverProperty;

    private NioEventLoopGroup bossGroup;

    private NioEventLoopGroup workGroup;


    public NettyBootstrapServer(ServerProperty serverProperty) {
        this.serverProperty = serverProperty;
        if (serverProperty.getHeart() < MIN_READ_TIMEOUT) {
            serverProperty.setHeart(MIN_READ_TIMEOUT);
        }
    }


    @Override
    public void start() {
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        bossGroup = new NioEventLoopGroup();
        workGroup = new NioEventLoopGroup();
        serverBootstrap.group(bossGroup, workGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_REUSEADDR, serverProperty.isReuseaddr())
                .option(ChannelOption.SO_BACKLOG, serverProperty.getBacklog())
                .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                .option(ChannelOption.SO_RCVBUF, serverProperty.getRevbuf())
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        doInitHandler(ch.pipeline());
                    }
                })
                .childOption(ChannelOption.TCP_NODELAY, serverProperty.isTcpNodelay())
                .childOption(ChannelOption.SO_KEEPALIVE, serverProperty.isKeepalive())
                .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
        serverBootstrap.bind(serverProperty.getPort())
                .addListener((ChannelFutureListener) channelFuture -> {
                    if (channelFuture.isSuccess()) {
                        log.info("服务端启动成功【本地:" + serverProperty.getPort() + "】");
                    } else {
                        log.info("服务端启动失败【本地:" + serverProperty.getPort() + "】");
                    }
                });
    }

    @Override
    public void shutdown() {
        if (workGroup != null && bossGroup != null) {
            try {
                bossGroup.shutdownGracefully().sync();
                workGroup.shutdownGracefully().sync();
            } catch (InterruptedException e) {
                log.error("服务端关闭资源失败【本地:" + serverProperty.getPort() + "】", e);
            }
        }
    }


    private void doInitHandler(ChannelPipeline channelPipeline) {
        if (serverProperty.isSsl()) {
            if (serverProperty.getJksCertificatePassword() == null || serverProperty.getJksFile() == null || serverProperty.getJksStorePassword() == null) {
                throw new NullPointerException("SSL file and password is null");
            }
            SSLContext sslContext = initSsl();
            SSLEngine engine = sslContext.createSSLEngine();
            engine.setUseClientMode(false);
            channelPipeline.addLast("ssl", new SslHandler(engine));
        }
        initProtocolHandler(channelPipeline);
//        channelPipeline.addLast(new IdleStateHandler(serverProperty.getHeart(),0,0));
    }


    private void initProtocolHandler(ChannelPipeline channelPipeline) {
        switch (serverProperty.getProtocols()) {
            case USER:
                FrameToMessageDecoder frameToMessageDecoder = new FrameToMessageDecoder();
                MessageToFrameEncoder messageToFrameEncoder = new MessageToFrameEncoder();

                frameToMessageDecoder.addFMessageTrait(FMTraits.FMTBytes);
                messageToFrameEncoder.addFMessageTrait(FMTraits.FMTBytes);

                frameToMessageDecoder.addFMessageTrait(FMTraits.FMTString);
                messageToFrameEncoder.addFMessageTrait(FMTraits.FMTString);

                frameToMessageDecoder.addFMessageTrait(FMTraits.FMTJson);
                messageToFrameEncoder.addFMessageTrait(FMTraits.FMTJson);

                channelPipeline.addLast(H_FRAME_DECODER, new FrameDecoder());
                channelPipeline.addLast(H_FRAME_ENCODER, new FrameEncoder());
                channelPipeline.addLast(H_READ_TIMEOUT, new ReadTimeoutHandler(serverProperty.getHeart(), TimeUnit.SECONDS));
                channelPipeline.addLast(H_FM_DECODER, frameToMessageDecoder);
                channelPipeline.addLast(H_FM_ENCODER, messageToFrameEncoder);
                channelPipeline.addLast(H_FM_HANDLER, new ServerHandler());
                break;
            case HTTP:
                break;
            case WS:
                break;
            default:break;
        }
    }


    private SSLContext initSsl() {
        String algorithm = SystemPropertyUtil.get("ssl.KeyManagerFactory.algorithm");
        if (algorithm == null) {
            algorithm = "SunX509";
        }
        SSLContext serverContext;
        try {
            //
            KeyStore ks = KeyStore.getInstance("JKS");
            ks.load(SecureSocketSslContextFactory.class.getResourceAsStream(serverProperty.getJksFile()), serverProperty.getJksStorePassword().toCharArray());
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
            kmf.init(ks, serverProperty.getJksCertificatePassword().toCharArray());
            serverContext = SSLContext.getInstance("TLS");
            serverContext.init(kmf.getKeyManagers(), null, null);
        } catch (Exception e) {
            throw new Error("Failed to initialize the server-side SSLContext", e);
        }
        return serverContext;
    }





}
